package de.jmda.core.util.ramcomp;

import static java.util.Arrays.asList;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

import de.jmda.core.util.JavaCompilerUtil;

public abstract class RAMCompiler
{
	private final static Logger LOGGER = Logger.getLogger(RAMCompiler.class);

	/**
	 * Container for data that is used or created by {@link
	 * RAMCompiler#compile(List)}. An instance represents a compilation unit plus
	 * a {@link ClassLoader} that can be used to load the type after compilation.
	 */
	public static class RAMCompilerData
	{
		/**
		 * Qualified name of the compilation unit represented by a {@link
		 * RAMCompilerData} instance
		 */
		private String qualifiedTypeName;

		/**
		 * Java source code of the compilation unit represented by a {@link
		 * RAMCompilerData} instance
		 */
		private String source;

		/**
		 * Class loader that can load the type represented by a {@link
		 * RAMCompilerData} instance after {@link RAMCompiler#compile(List)}
		 */
		private ClassLoader classLoader;

		public RAMCompilerData(String qualifiedTypeName, String source)
		{
			if (StringUtils.isBlank(qualifiedTypeName))
			{
				throw new IllegalArgumentException("qualified type name must not be null");
			}

			if (StringUtils.isBlank(source))
			{
				throw new IllegalArgumentException("source must not be null");
			}

			this.qualifiedTypeName = qualifiedTypeName;
			this.source = source;
		}

		public ClassLoader getClassLoader()
		{
			return classLoader;
		}

		public void setClassLoader(ClassLoader classLoader)
		{
			this.classLoader = classLoader;
		}

		public String getQualifiedTypeName()
		{
			return qualifiedTypeName;
		}

		public String getSource()
		{
			return source;
		}
	}

	/**
	 * Compiles types represented by <code>items</code> and updates <code>items
	 * </code> so that they contain a {@link ClassLoader} that can load the types. 
	 * 
	 * @param items
	 * @throws IOException 
	 */
	public static void compile(List<RAMCompilerData> items) throws IOException
	{
		JavaCompiler compiler = JavaCompilerUtil.createJavaCompiler();

		List<JavaFileObject> javaFileObjects = new ArrayList<>();

		for (RAMCompilerData item : items)
		{
			javaFileObjects.add(
					new JavaSourceCodeObject(item.qualifiedTypeName, item.source));
		}

		DiagnosticCollector<JavaFileObject> diagnosticCollector =
		    new DiagnosticCollector<JavaFileObject>();

		JavaFileManager fileManager =
		    new RAMCompilerClassFileManager(
		    		compiler.getStandardFileManager(null, null, null));
		
		boolean result;

		try
		{
			CompilationTask task =
					compiler.getTask(
							null, fileManager, diagnosticCollector, null, null, javaFileObjects);

			result = task.call();
		}
		finally
		{
			fileManager.close();
		}

		if (false == result)
		{
			String msg =
					JavaCompilerUtil.diagnosticCollectorAsString(diagnosticCollector);

			LOGGER.error(msg);

			throw new IOException(msg);
		}

		for (RAMCompilerData item : items)
		{
			item.classLoader = fileManager.getClassLoader(null);
		}
	}

	public static void compile(RAMCompilerData... items) throws IOException
	{
		compile(asList(items));
	}
}